// 版权归Epic Games, Inc.所有。保留所有权利。

#pragma once

#include "AsyncMixin.h"
#include "Blueprint/UserWidgetPool.h"
#include "Widgets/SPanel.h"

class FActiveTimerHandle;
class FArrangedChildren;
class FChildren;
class FPaintArgs;
class FReferenceCollector;
class FSlateRect;
class FSlateWindowElementList;
class FWidgetStyle;
class UIndicatorDescriptor;
class ULyraIndicatorManagerComponent;
struct FSlateBrush;

// 角色画布类，继承自SPanel、FAsyncMixin和FGCObject
class SActorCanvas : public SPanel, public FAsyncMixin, public FGCObject
{
public:
	/** ActorCanvas特定的插槽类 */
	class FSlot : public TSlotBase<FSlot>
	{
	public: 

		/**
		 * 构造函数
		 * @param InIndicator 指示器描述符
		 */
		FSlot(UIndicatorDescriptor* InIndicator)
			: TSlotBase<FSlot>()
			, Indicator(InIndicator) // 指示器描述符
			, ScreenPosition(FVector2D::ZeroVector) // 屏幕位置
			, Depth(0) // 深度
			, Priority(0.f) // 优先级
			, bIsIndicatorVisible(true) // 指示器是否可见
			, bInFrontOfCamera(true) // 是否在相机前方
			, bHasValidScreenPosition(false) // 是否有有效的屏幕位置
			, bDirty(true) // 是否脏
			, bWasIndicatorClamped(false) // 指示器是否被限制
			, bWasIndicatorClampedStatusChanged(false) // 指示器限制状态是否改变
		{
		}

		SLATE_SLOT_BEGIN_ARGS(FSlot, TSlotBase<FSlot>)
		SLATE_SLOT_END_ARGS()
		using TSlotBase<FSlot>::Construct; // 使用父类构造

		/**
		 * 获取指示器是否可见
		 * @return 是否可见
		 */
		bool GetIsIndicatorVisible() const { return bIsIndicatorVisible; }
		/**
		 * 设置指示器是否可见
		 * @param bVisible 是否可见
		 */
		void SetIsIndicatorVisible(bool bVisible)
		{
			if (bIsIndicatorVisible != bVisible)
			{
				bIsIndicatorVisible = bVisible; // 设置可见性
				bDirty = true; // 标记为脏
			}

			RefreshVisibility(); // 刷新可见性
		}

		/**
		 * 获取屏幕位置
		 * @return 屏幕位置
		 */
		FVector2D GetScreenPosition() const { return ScreenPosition; }
		/**
		 * 设置屏幕位置
		 * @param InScreenPosition 屏幕位置
		 */
		void SetScreenPosition(FVector2D InScreenPosition)
		{
			if (ScreenPosition != InScreenPosition)
			{
				ScreenPosition = InScreenPosition; // 设置屏幕位置
				bDirty = true; // 标记为脏
			}
		}

		/**
		 * 获取深度
		 * @return 深度
		 */
		double GetDepth() const { return Depth; }
		/**
		 * 设置深度
		 * @param InDepth 深度
		 */
		void SetDepth(double InDepth)
		{
			if (Depth != InDepth)
			{
				Depth = InDepth; // 设置深度
				bDirty = true; // 标记为脏
			}
		}

		/**
		 * 获取优先级
		 * @return 优先级
		 */
		int32 GetPriority() const { return Priority; }
		/**
		 * 设置优先级
		 * @param InPriority 优先级
		 */
		void SetPriority(int32 InPriority)
		{
			if (Priority != InPriority)
			{
				Priority = InPriority; // 设置优先级
				bDirty = true; // 标记为脏
			}
		}

		/**
		 * 获取是否在相机前方
		 * @return 是否在相机前方
		 */
		bool GetInFrontOfCamera() const { return bInFrontOfCamera; }
		/**
		 * 设置是否在相机前方
		 * @param bInFront 是否在相机前方
		 */
		void SetInFrontOfCamera(bool bInFront)
		{
			if (bInFrontOfCamera != bInFront)
			{
				bInFrontOfCamera = bInFront; // 设置是否在相机前方
				bDirty = true; // 标记为脏
			}

			RefreshVisibility(); // 刷新可见性
		}

		/**
		 * 获取是否有有效的屏幕位置
		 * @return 是否有有效的屏幕位置
		 */
		bool HasValidScreenPosition() const { return bHasValidScreenPosition; }
		/**
		 * 设置是否有有效的屏幕位置
		 * @param bValidScreenPosition 是否有有效的屏幕位置
		 */
		void SetHasValidScreenPosition(bool bValidScreenPosition)
		{
			if (bHasValidScreenPosition != bValidScreenPosition)
			{
				bHasValidScreenPosition = bValidScreenPosition; // 设置是否有有效的屏幕位置
				bDirty = true; // 标记为脏
			}

			RefreshVisibility(); // 刷新可见性
		}

		/**
		 * 获取是否脏
		 * @return 是否脏
		 */
		bool bIsDirty() const { return bDirty; }

		/**
		 * 清除脏标志
		 */
		void ClearDirtyFlag()
		{
			bDirty = false; // 清除脏标志
		}

		/**
		 * 获取指示器是否被限制
		 * @return 是否被限制
		 */
		bool WasIndicatorClamped() const { return bWasIndicatorClamped; }
		/**
		 * 设置指示器是否被限制
		 * @param bWasClamped 是否被限制
		 */
		void SetWasIndicatorClamped(bool bWasClamped) const
		{
			if (bWasClamped != bWasIndicatorClamped)
			{
				bWasIndicatorClamped = bWasClamped; // 设置是否被限制
				bWasIndicatorClampedStatusChanged = true; // 标记限制状态改变
			}
		}

		/**
		 * 获取指示器限制状态是否改变
		 * @return 限制状态是否改变
		 */
		bool WasIndicatorClampedStatusChanged() const { return bWasIndicatorClampedStatusChanged; }
		/**
		 * 清除指示器限制状态改变标志
		 */
		void ClearIndicatorClampedStatusChangedFlag()
		{
			bWasIndicatorClampedStatusChanged = false; // 清除限制状态改变标志
		}

	private:
		/**
		 * 刷新可见性
		 */
		void RefreshVisibility()
		{
			// 计算是否可见：指示器可见且有有效的屏幕位置
			const bool bIsVisible = bIsIndicatorVisible && bHasValidScreenPosition;
			// 设置小部件可见性
			GetWidget()->SetVisibility(bIsVisible ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed);
		}

		//由SActorCanvas::AddReferencedObjects保持存活
		UIndicatorDescriptor* Indicator; // 指示器描述符
		FVector2D ScreenPosition; // 屏幕位置
		double Depth; // 深度
		int32 Priority; // 优先级

		uint8 bIsIndicatorVisible : 1; // 指示器是否可见（位域）
		uint8 bInFrontOfCamera : 1; // 是否在相机前方（位域）
		uint8 bHasValidScreenPosition : 1; // 是否有有效的屏幕位置（位域）
		uint8 bDirty : 1; // 是否脏（位域）
		
		/** 
		 * 缓存的、帧延迟的值，表示指示器上一帧是否在视觉上被屏幕限制；
		 * 半hacky的可变实现，因为它在常量绘制操作期间被缓存
		 */
		mutable uint8 bWasIndicatorClamped : 1; // 指示器是否被限制（可变位域）
		mutable uint8 bWasIndicatorClampedStatusChanged : 1; // 指示器限制状态是否改变（可变位域）

		friend class SActorCanvas; // 友元类
	};

	/** ActorCanvas特定的箭头插槽类 */
	class FArrowSlot : public TSlotBase<FArrowSlot>
	{
	};

	/** 开始这个Slate小部件的参数 */
	SLATE_BEGIN_ARGS(SActorCanvas) {
		_Visibility = EVisibility::HitTestInvisible; // 默认可见性为命中测试不可见
	}

		/** 表示我们有一个这个widget支持的插槽 */
		SLATE_SLOT_ARGUMENT(SActorCanvas::FSlot, Slots) // Slots参数
	
	/** 这个总是放在最后 */
	SLATE_END_ARGS()

	/**
	 * 构造函数
	 */
	SActorCanvas()
		: CanvasChildren(this) // 画布子项
		, ArrowChildren(this) // 箭头子项
		, AllChildren(this) // 所有子项
	{
		AllChildren.AddChildren(CanvasChildren); // 添加画布子项
		AllChildren.AddChildren(ArrowChildren); // 添加箭头子项
	}

	/**
	 * 构造函数
	 * @param InArgs 参数
	 * @param InCtx 本地玩家上下文
	 * @param ActorCanvasArrowBrush 角色画布箭头画刷
	 */
	void Construct(const FArguments& InArgs, const FLocalPlayerContext& InCtx, const FSlateBrush* ActorCanvasArrowBrush);

	// SWidget接口
	/**
	 * 排列子项
	 * @param AllottedGeometry 分配的几何体
	 * @param ArrangedChildren 排列的子项
	 */
	virtual void OnArrangeChildren( const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren ) const override;
	/**
	 * 计算期望尺寸
	 * @param 缩放
	 * @return 期望尺寸
	 */
	virtual FVector2D ComputeDesiredSize(float) const override { return FVector2D::ZeroVector; }
	/**
	 * 获取子项
	 * @return 子项指针
	 */
	virtual FChildren* GetChildren() override { return &AllChildren; }
	/**
	 * 绘制函数
	 * @param Args 绘制参数
	 * @param AllottedGeometry 分配的几何体
	 * @param MyCullingRect 我的剔除矩形
	 * @param OutDrawElements 输出绘制元素列表
	 * @param LayerId 图层ID
	 * @param InWidgetStyle 小部件样式
	 * @param bParentEnabled 父级是否启用
	 * @return 最大图层ID
	 */
	virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const;
	// 结束SWidget接口

	/**
	 * 设置是否按顺序绘制元素
	 * @param bInDrawElementsInOrder 是否按顺序绘制元素
	 */
	void SetDrawElementsInOrder(bool bInDrawElementsInOrder) { bDrawElementsInOrder = bInDrawElementsInOrder; }

	/**
	 * 获取引用者名称
	 * @return 引用者名称
	 */
	virtual FString GetReferencerName() const override;
	/**
	 * 添加引用对象
	 * @param Collector 引用收集器
	 */
	virtual void AddReferencedObjects( FReferenceCollector& Collector ) override;
	
private:
	/**
	 * 指示器添加回调
	 * @param Indicator 指示器描述符
	 */
	void OnIndicatorAdded(UIndicatorDescriptor* Indicator);
	/**
	 * 指示器移除回调
	 * @param Indicator 指示器描述符
	 */
	void OnIndicatorRemoved(UIndicatorDescriptor* Indicator);

	/**
	 * 为条目添加指示器
	 * @param Indicator 指示器描述符
	 */
	void AddIndicatorForEntry(UIndicatorDescriptor* Indicator);
	/**
	 * 为条目移除指示器
	 * @param Indicator 指示器描述符
	 */
	void RemoveIndicatorForEntry(UIndicatorDescriptor* Indicator);

	using FScopedWidgetSlotArguments = TPanelChildren<FSlot>::FScopedWidgetSlotArguments; // 作用域小部件插槽参数类型
	/**
	 * 添加角色插槽
	 * @param Indicator 指示器描述符
	 * @return 作用域小部件插槽参数
	 */
	FScopedWidgetSlotArguments AddActorSlot(UIndicatorDescriptor* Indicator);
	/**
	 * 移除角色插槽
	 * @param SlotWidget 插槽小部件
	 * @return 移除的索引
	 */
	int32 RemoveActorSlot(const TSharedRef<SWidget>& SlotWidget);

	/**
	 * 设置是否显示任何指示器
	 * @param bIndicators 是否显示指示器
	 */
	void SetShowAnyIndicators(bool bIndicators);
	/**
	 * 更新画布
	 * @param InCurrentTime 当前时间
	 * @param InDeltaTime 增量时间
	 * @return 活动计时器返回类型
	 */
	EActiveTimerReturnType UpdateCanvas(double InCurrentTime, float InDeltaTime);

	/** 计算偏移的辅助函数 */
	/**
	 * 获取偏移和尺寸
	 * @param Indicator 指示器描述符
	 * @param OutSize 输出尺寸
	 * @param OutOffset 输出偏移
	 * @param OutPaddingMin 输出最小内边距
	 * @param OutPaddingMax 输出最大内边距
	 */
	void GetOffsetAndSize(const UIndicatorDescriptor* Indicator,
		FVector2D& OutSize, 
		FVector2D& OutOffset,
		FVector2D& OutPaddingMin,
		FVector2D& OutPaddingMax) const;

	/**
	 * 更新活动计时器
	 */
	void UpdateActiveTimer();

private:
	TArray<TObjectPtr<UIndicatorDescriptor>> AllIndicators; // 所有指示器对象指针数组
	TArray<UIndicatorDescriptor*> InactiveIndicators; // 非活动指示器数组
	
	FLocalPlayerContext LocalPlayerContext; // 本地玩家上下文
	TWeakObjectPtr<ULyraIndicatorManagerComponent> IndicatorComponentPtr; // 指示器组件弱引用

	/** 这个画布中的所有插槽 */
	TPanelChildren<FSlot> CanvasChildren; // 画布子项
	mutable TPanelChildren<FArrowSlot> ArrowChildren; // 箭头子项（可变）
	FCombinedChildren AllChildren; // 所有子项

	FUserWidgetPool IndicatorPool; // 指示器小部件池

	const FSlateBrush* ActorCanvasArrowBrush = nullptr; // 角色画布箭头画刷

	mutable int32 NextArrowIndex = 0; // 下一个箭头索引（可变）
	mutable int32 ArrowIndexLastUpdate = 0; // 上次更新的箭头索引（可变）

	/** 是否按添加顺序绘制元素。注意：启用此选项将禁用批处理，并导致更多的绘制调用 */
	bool bDrawElementsInOrder = false; // 是否按顺序绘制元素

	bool bShowAnyIndicators = false; // 是否显示任何指示器

	mutable TOptional<FGeometry> OptionalPaintGeometry; // 可选绘制几何体（可变）

	TSharedPtr<FActiveTimerHandle> TickHandle; // 计时器句柄共享指针
};